<template>
  <a-form class="form" ref="form" @validate="validate" v-bind="$attrs" @keyup.native.enter="onEnter">
    <slot></slot>
    <render :data="stepResult"></render>
    <slot name="footer"></slot>
  </a-form>
</template>
<script>
export default {
  name:'ExForm',
  inheritAttrs: false,
}
</script>
<script setup>
import axios from 'axios'
import { watchDebounced } from '@vueuse/core'
import {useInjectRenderContext} from "@/components/render/context";
import { forEach,debounce ,cloneDeep } from 'lodash'
import useHttp from '@/hook/use-http'
import request from '@/utils/axios'
import {isNumber, exceptField, compareArrToAttr} from '@/utils'
import {useProvideFormContext} from './context'
import {ref,useAttrs,nextTick,isReactive,computed,watch} from 'vue'
const attrs = useAttrs()
const emit = defineEmits(['submit','success','update:stepCurrent','watchModel'])
const props = defineProps({
  //请求地址
  url:String,
  //请求方式
  method: {
    type:String,
    default:'POST',
  },
  //排除提交字段
  exceptField:{
    type:Array,
    default:[],
  },
  editId:{
    type:[String,Number],
    default:null,
  },
  callParams:{
    type:Object,
    default:{},
  },
  params:{
    type:Object,
    default:{},
  },
  tabsValidateField:{
    type:Array,
    default:[],
  },
  collapseValidateField:{
    type:Array,
    default:[],
  },
  validateField:{
    type:Array,
    default:[],
  },
  watchField:{
    type:Array,
    default:[],
  },
  stepCurrent:[String,Number],
  enterToTab:Boolean,
  focusFirst:Boolean
})
const form = ref()
const originData = submitData()
const formModel = attrs.model
const watchData = []
const proxyData = useInjectRenderContext()
const stepResult = ref(null)
const {loading,http} = useHttp()
//回车选中焦点index
let enterTabIndex = -1
let submitPass = true
//watch 请求取消
let watchCancel = null
watchDebounced(attrs.model,value=>{
  emit('watchModel',value)
},{ debounce: 300})

props.watchField.forEach(item=>{
  let field = item.field
  let watchField = getWatchField(field)
  const watchValue = eval('attrs.model.'+watchField)
  if(isReactive(watchValue)){
    watchDebounced(computed(()=>{
      return JSON.stringify(eval('attrs.model.'+watchField))
    }),(newValue,oldValue)=>{
      if(newValue === oldValue){
        return
      }
      newValue = JSON.parse(newValue)
      exceptField(newValue,props.exceptField)
      oldValue = JSON.parse(oldValue)
      if(field.indexOf('.*.') > -1){
        compareArrToAttr(newValue,oldValue,getWatchField(field,true)).forEach(item=>{
          watchQueue(field,item.value,item.oldValue,item.index)
        })
      }else{
        watchQueue(field,newValue,oldValue)
      }
    },{debounce: item.debounce,deep:true})
  }else{

    watchDebounced(()=>eval('attrs.model.'+watchField),(newValue,oldValue)=>{

      watchQueue(field,newValue,oldValue)
    },{ debounce: 300})
  }
})
function watchQueue(field,newValue,oldValue,index=undefined){

  const length = watchData.length

  if((newValue === null && oldValue === "") || (oldValue === null && newValue === "")){
    return
  }
  watchData.push({
    field:field,
    newValue:newValue,
    oldValue:oldValue,
    index:index,
  })
  if(length === 0){

    watchListen()
  }
}
//获取转换监听字段
function getWatchField(field,end=false){
  let watchField = field
  if(field.indexOf('.*.') > -1){
    let fields = field.split('.*.')
    if(end){
      watchField = fields[1]
    }else{
      watchField = fields[0]
    }
  }
  return watchField
}
//监听watch变化数据队列执行
function watchListen(){
  const copyData = JSON.parse(JSON.stringify(watchData))
  const data = copyData.shift()
  if(data){
    watchAjax(data.field,data.newValue,data.oldValue,data.index)
    watchData.shift()
    watchListen()
  }
}
//watch ajax请求
function watchAjax(field,newValue,oldValue,index){

  if(watchCancel != null){
    watchCancel()
  }
  return new Promise((resolve,reject) => {
    submitPass = false
    request({
      url:props.url,
      method:props.method,
      cancelToken:new axios.CancelToken(function executor(c){
        watchCancel = c
      }),
      data: Object.assign(props.callParams,{
        ex_admin_form_action: 'watch',
        originData:originData,
        data:attrs.model,
        ex_field:field,
        newValue:newValue,
        oldValue:oldValue,
        index:index,
        formField:attrs.formField,
        id:props.editId
      },props.params)
    }).then(res=>{
      watchCancel = null
      let formData = res.data.data
      for(let f in formData){
        if(isReactive(attrs.model[f])){
          let exceptField = getWatchField(field,true)
          for (let index in formData[f]){
            try {
              formData[f][index][exceptField] = attrs.model[f][index][exceptField]
            }catch (e){

            }
          }
        }
        attrs.model[f] = formData[f]
      }
      submitPass = true
    }).finally((res)=>{
      resolve(res)
    })
  })
}
//第一个获取焦点
if(props.focusFirst){
  nextTick(()=>{
    enterNext()
  })
}
//回车下一个元素获取焦点
function enterNext(){
  let elements = form.value.$el.querySelectorAll("input,select,textarea")
  elements = Array.from(elements).filter(el=>{
    if(el.nodeName == 'INPUT' && el.getAttribute("type") == 'hidden'){
      return false
    }
    return true
  })
  elements.forEach((el,index)=>{
    if(document.activeElement === el){
      enterTabIndex = index
    }
  })
  if(enterTabIndex < elements.length-1){
    enterTabIndex++
  }else{
    enterTabIndex = 0
  }
  elements[enterTabIndex].focus()
}
//回车事件
function onEnter(){
  if(props.enterToTab){
    enterNext()
  }
}
function submitData(){
  const submitData = cloneDeep(attrs.model)
  exceptField(submitData,props.exceptField)
  for(let key in submitData){
    if(submitData[key] === undefined){
      submitData[key] = null
    }
  }
  return submitData
}
function scrollToField(name){
  //跳转到对应tab显示
  let validateField = name.map(key=>{
    return isNumber(key)?'*':key
  })
  forEach(props.tabsValidateField,function (item){
    forEach(item,function (tabs,field){
      if(validateField.join('.') == field){
        forEach(tabs,function (tab){
          proxyData[tab.model] = tab.key
        })
      }
    })
  })
  forEach(props.collapseValidateField,function (item){
    forEach(item,function (tabs,field){
      if(validateField.join('.') == field){
        forEach(tabs,function (tab){
          proxyData[tab.model] = tab.key
        })
      }
    })
  })
  form.value.scrollToField(name,{
    block: 'center',
    behavior: 'smooth'
  })
}
function validate(name, status, errorMsgs){
  name = name.join('.')
  if(status){
    props.validateField[name].message = null
    props.validateField[name].status = null
  }else{
    props.validateField[name].status = 'error'
    props.validateField[name].message = errorMsgs
  }
}
function clearValidate(){
  forEach(props.validateField,function (item,name) {
    props.validateField[name].message = null
    props.validateField[name].status = null
  })
}
const submitDebounce = debounce(submit,500)
function submit(){
  const data = submitData()
  emit('submit',data)
  form.value.validate().then(()=>{
    if(props.url){
      //等待watch完成
      if(watchData.length > 0 || !submitPass){
        loading.value = true
        setTimeout(()=>{
          submit()
        },100)
        return
      }
      let submitObject = {
        ex_admin_form_action: 'save',
        originData:originData,
        data:data,
        CURRENT_VALIDATION_STEP : props.stepCurrent,
        FORM_REF : attrs.form_ref,
      }
      submitObject.id = props.editId
      http({
        url:props.url,
        method:props.method,
        data:Object.assign(props.callParams,submitObject,props.params)
      }).then(result=>{
        clearValidate()
        if(result.code === 422){
          //验证规则不通过
          let firstField=''
          for (let field in result.data){
            if(!firstField){
              firstField = field
            }
            let message = result.data[field]
            props.validateField[field].status = 'error'
            props.validateField[field].message = Array.isArray(message) ? message[0]:message
          }
          scrollToField([firstField])
          return
        }else if(result.code === 201){
          emit('update:stepCurrent',props.stepCurrent+1)
          return
        }else if(result.code === 202){
          emit('update:stepCurrent',props.stepCurrent+1)
          stepResult.value = result.data
        }
        emit('success',result)
      })
    }else {
      emit('success')
    }
  }).catch(error=>{

    scrollToField(error.errorFields[0].name)
  })
}
//重置步骤表单
function stepReset(){
  emit('update:stepCurrent',0)
  form.value.resetFields()
  stepResult.value = null
}
function reset(){
  form.value.resetFields()
  clearValidate()
}
const context = {
  submitData,
  submitDebounce,
  stepReset,
  form,
  loading,
  submit,
  reset
}
useProvideFormContext(context)
defineExpose(context)
</script>

<style scoped lang="scss">
.form{
  background: #fff;
  border-radius: 4px;
  padding: 20px;
}
.dark-theme{
  .form {
    background: #1f1f1f;
  }
}
.ant-modal-body .form{
  padding: 0;
}
</style>
